#include <pthread.h>
#include "carADT.h"
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include "semaphoreADT.h"
#include <time.h>
#include <errno.h>


enum POSSIBLE_DIRECTIONS{RIGHT, LEFT, FORWARD};

#define MAX_PATERN_SIZE 8
#define PATTERN_END -1
#define WAIT_TIME_SECONDS       3
#define MAX_SPEED_CARS		20000
#define MIN_SPEED_CARS		5000000

struct carCDT{
	cityADT city;
	int speedMultiplier;
	double speed;
	int drawSpeed;
	int direction; //of type CAR_DIRECTION
	//used to see if previus tile vas a corner
	int pattern[MAX_PATERN_SIZE];
	int hasPattern;
	int currentPaternPos;
	int x;
	int y;
	int isKilled;
	int uniqueId;
	int isInFirstCorner;
	drawerADT drawer;
	pthread_mutex_t mutex;
	pthread_cond_t conditionalVar;
} carCDT;

//parameter of type speed
static int getSpeed(int speed);
static int getRandomDirection();
static void moveCar(carADT car);
static void updatePosition(carADT car);
static int newDirection(carADT car, int nextDir);
static int transformToDrawerDirection(int carDirection);
static int getDrawerColor(int carSpeedMultiplier);
static int waitCarToMove(carADT blockingCar);
static void setRandomPatern(carADT car);
static void followPatern(carADT car);
static int isVertical(int lastDirection);
static void getSemaphoreCoordenates(int x, int y, int direction, int* semaphoreX, int* semaphoreY);
static void drawSemaphoreIfAny(cityADT city, int x, int y);
static void stopEngine(carADT car);
static void waitCarToMoveLock(carADT blockingCar);
static void startEngine(carADT car);

void
dummyCar()
{
	return;
}


int
getCarX(carADT car)
{
	return car->x;
}

int
getCarY(carADT car)
{
	return car->y;
}

int
getCarId(carADT car)
{
	return car->uniqueId;
}


carADT
newCar(int speed, int initPosX, int initPosY, cityADT city, int direction, int rSeed, int uniqueId, drawerADT drawer)
{
	carADT car;
	pthread_attr_t attr;
	pthread_t thread;

	car = malloc(sizeof(carCDT));
	if(car == NULL)
		return NULL;

	car->isKilled = 0;
	car->drawer = drawer;
	car->drawSpeed = speed;
	car->speedMultiplier = getSpeed(speed);
	car->direction = direction;
	car->city = city;
	car->x = initPosX;
	car->y = initPosY;
	car->uniqueId = uniqueId;
	car->isInFirstCorner = 0;
	
	car->speed = BASE_SPEED / car->speedMultiplier;
	
	car->pattern[0] = PATTERN_END;
	car->hasPattern = 0;
	car->currentPaternPos = 0;


	//set seed used for the randomnes of the car movement
	srand(rSeed);
	
	pthread_mutex_init(&car->mutex, NULL);
	pthread_cond_init(&car->conditionalVar, NULL);


	pthread_attr_init(&attr);
	pthread_attr_setstacksize( &attr, 50 * 1024);
	pthread_create(&thread, &attr, (void * (*) (void *)) startEngine, (void *) car); 

	return car;
}

static int 
getRandomDirection()
{
	return rand() % 3;
}

void
resetSpeeds(carADT car)
{
	car->speed = BASE_SPEED / car->speedMultiplier;
	return;
}


//parameter of type SPEED
static int 
getSpeed(int speed)
{
	switch(speed)
	{
		case CAR_SLOW:
			return BASE_SPEED_MULTIPLIER - 1;
		case CAR_NORMAL:
			return BASE_SPEED_MULTIPLIER;
		case CAR_FAST:
			return BASE_SPEED_MULTIPLIER + 1;
		case CAR_CRAZY:
			return BASE_SPEED_MULTIPLIER + 2;
		default:
			return BASE_SPEED_MULTIPLIER;
	}
}

void 
setPos(carADT car, int posX, int posY, int direction)
{
	pthread_mutex_lock(&car->mutex);
	car->x = posX;
	car->y = posY;
	car->direction = direction;
	pthread_mutex_unlock(&car->mutex);
	return;
}

int
multiplySpeed(carADT car, double multiplier)
{
	if(multiplier < 0.00)
		return 0;

	car->speed /= multiplier;
	if(car->speed < MAX_SPEED_CARS)
	{
		car->speed = MAX_SPEED_CARS;
		return 0;
	}
	else if (car->speed > MIN_SPEED_CARS)
	{
		car->speed = MIN_SPEED_CARS;
		return 0;
	}
	return 1;
}

static void
startEngine(carADT car)
{
	carADT blockingCar;	
	int lastXPosition, lastYPosition, lastDirection, nextXPosition, nextYPosition, nextDirection;
	int drawerDirection, drawerColor, semaphoreX, semaphoreY;
	semaphoreADT semaphoreInRed;

	
	while(1)
	{
		//pthread_mutex_lock(&car->mutex);
		semaphoreInRed = NULL;
		blockingCar = NULL;
		//pthread_mutex_unlock(&car->mutex);
		
		usleep(car->speed);
		

		//guardar posisiones originales
		lastXPosition = car->x;
		lastYPosition = car->y;
		lastDirection = car->direction;
		
		//PUEDO AVANZAR CAR y SEMAPHORE MUTEX??
		pthread_mutex_lock(&car->mutex);
		moveCar(car);

		if(!car->hasPattern && isCorner(car->city, car->x, car->y))
		{
			getSemaphoreCoordenates(car->x, car->y, car->direction,
				 &semaphoreX, &semaphoreY);
			
			semaphoreInRed = hasSemaphoreInRed(car->city, semaphoreX,
				semaphoreY, isVertical(lastDirection)?SEM_VER:SEM_HOR);
		}
		pthread_mutex_unlock(&car->mutex);
		if( semaphoreInRed != NULL)
		{
			pthread_mutex_lock(&car->mutex);
			nextXPosition = car->x;
			nextYPosition = car->y;
			nextDirection = car->direction;
			car->x = lastXPosition;
			car->y = lastYPosition;
			car->direction = lastDirection;
			pthread_mutex_unlock(&car->mutex);
			if(semaphoreInRed != NULL)
				waitSemaphoreToChange(semaphoreInRed,
					isVertical(lastDirection)?SEM_VER:SEM_HOR);
					

			pthread_mutex_lock(&car->mutex);
			car->x = nextXPosition;
			car->y = nextYPosition;
			car->direction = nextDirection;
			pthread_mutex_unlock(&car->mutex);
		}
		pthread_mutex_lock(&car->mutex);
		blockingCar = hasThisPositionACar(car->city, car->x, car->y, car->uniqueId);
		pthread_mutex_unlock(&car->mutex);
		//Is there a car blocking me or a semaphore in red?
		if(blockingCar != NULL )
		{
			pthread_mutex_lock(&car->mutex);
			nextXPosition = car->x;
			nextYPosition = car->y;
			nextDirection = car->direction;
			car->x = lastXPosition;
			car->y = lastYPosition;
			car->direction = lastDirection;
	
			pthread_mutex_unlock(&car->mutex);
			if(blockingCar != NULL)
			{
				if(isCorner(car->city, car->x, car->y) && car->isInFirstCorner)
				{
					if(!waitCarToMove(blockingCar))
					{
						setRandomPatern(car);
						continue;
					}
				}else
						waitCarToMoveLock(blockingCar);
			}
				/*while(!waitCarToMove(blockingCar))
				{
					pthread_mutex_lock(&car->mutex);
					//I have to find an alternative route
					if(isCorner(car->city, car->x, car->y) && !car->hasPattern)
					{
						moveCarToTheRight(car);
						nextXPosition = car->x;
						nextYPosition = car->y;
						nextDirection = car->direction;
						//pthread_cond_broadcast(&car->conditionalVar);
					}
					pthread_mutex_unlock(&car->mutex);
				}*/

			pthread_mutex_lock(&car->mutex);
			car->x = nextXPosition;
			car->y = nextYPosition;
			car->direction = nextDirection;
			pthread_mutex_unlock(&car->mutex);
		}
	
		//My car moved. This position is ocuppid
		
		pthread_mutex_lock(&car->mutex);
		//delete and draw me
		removeObject(car->drawer, lastXPosition, lastYPosition);
		//redraw the semaphore i passed
		if(isCorner(car->city, lastXPosition, lastYPosition))
		{
			getSemaphoreCoordenates(lastXPosition, lastYPosition, lastDirection,
				 &semaphoreX, &semaphoreY);
			drawSemaphoreIfAny(car->city, semaphoreX, semaphoreY);
		}
		
		drawerDirection = transformToDrawerDirection(car->direction);
		drawerColor = getDrawerColor(car->drawSpeed);
		addCar(car->drawer, drawerDirection, drawerColor, car->x, car->y);
		draw(car->drawer);

		pthread_cond_broadcast(&car->conditionalVar);
		pthread_mutex_unlock(&car->mutex);


		if(car->isKilled)
			stopEngine(car);

		
	}

}

static void
stopEngine(carADT car)
{
	unRegisterCar(car->city, car);
	removeObject(car->drawer, car->x, car->y);
	pthread_cond_broadcast(&car->conditionalVar);
	pthread_mutex_unlock(&car->mutex);
	pthread_cond_destroy(&car->conditionalVar);
	pthread_mutex_destroy(&car->mutex);
	free(car);
	pthread_exit(NULL);
}

static void
getSemaphoreCoordenates(int x, int y , int direction, int* semaphoreX, int* semaphoreY)
{

	switch(direction)
	{
		case CAR_N:
			*semaphoreY = y - 1;
			*semaphoreX = x - 1;
			break;
		case CAR_S:
			*semaphoreY = y;
			*semaphoreX = x;
			break;
		case CAR_E:
			*semaphoreX = x;
			*semaphoreY = y - 1;
			break;
		case CAR_W:
			*semaphoreY = y;
			*semaphoreX = x - 1;
			break;
		default:
			break;
	}

	return;
}

static int
isVertical(int lastDirection)
{
	if(lastDirection == CAR_N || lastDirection == CAR_S)
		return 1;
	else
		return 0;
}

static int
transformToDrawerDirection(int carDirection)
{
	switch(carDirection)
	{
		case CAR_N:
			return N;
		case CAR_S:
			return S;
		case CAR_E:
			return E;
		case CAR_W:
			return W;
		default:
			return -1;
	}
}


static int
getDrawerColor(int carSpeedMultiplier)
{
	switch(carSpeedMultiplier)
	{
		case CAR_SLOW:
			return YELLOW;
		case CAR_NORMAL:
			return CYAN;
		case CAR_FAST:
			return MAGENTA;
		case CAR_CRAZY:
			return RED;
		default:
			return BLUE;
	}
}

static void
waitCarToMoveLock(carADT blockingCar)
{
	pthread_mutex_lock(&blockingCar->mutex);
	pthread_cond_wait(&blockingCar->conditionalVar, &blockingCar->mutex);
	pthread_mutex_unlock(&blockingCar->mutex);
}

static int
waitCarToMove(carADT blockingCar)
{
	int rc;
	struct timespec   ts;
  	struct timeval    tp;
 
  	gettimeofday(&tp, NULL); 	
  	ts.tv_sec  = tp.tv_sec;
   	ts.tv_nsec = tp.tv_usec * 1000;
   	ts.tv_sec += WAIT_TIME_SECONDS;
    
	pthread_mutex_lock(&blockingCar->mutex);
	rc=pthread_cond_timedwait(&blockingCar->conditionalVar, &blockingCar->mutex, &ts);
	//pthread_cond_wait(&blockingCar->conditionalVar, &blockingCar->mutex);
	pthread_mutex_unlock(&blockingCar->mutex);
	if(rc == ETIMEDOUT)
		return 0;
		
	return 1;
}



static void
moveCar(carADT car)
{
	if(car->hasPattern)
	{
		followPatern(car);
	}
	else
	{
		if(isCorner(car->city, car->x, car->y))
		{	
			setRandomPatern(car);
			followPatern(car);
		}
	}
	updatePosition(car);
	

	return;

}

static void
drawSemaphoreIfAny(cityADT city, int x, int y)
{
	semaphoreADT sem;

	sem = getSemaphore(city, x, y);
	if(sem == NULL)
	{
		return;
	}
	drawSem(sem);
	return;
}

static void
setRandomPatern(carADT car)
{
	int objective;

	objective = getRandomDirection();

	switch(objective)
	{
		case FORWARD:
			car->pattern[0] = FORWARD;
			car->pattern[1] = FORWARD;
			car->pattern[2] = PATTERN_END;
			break;
		case RIGHT:
			car->pattern[0] = RIGHT;
			car->pattern[1] = PATTERN_END;
			break;
		default:
			car->pattern[0] = RIGHT;
			car->pattern[1] = PATTERN_END;
			break;
	}

	car->hasPattern = 1;
	car->currentPaternPos = 0;
	
	return;
	
}

static void
followPatern(carADT car)
{
	int nextDir;
	nextDir = car->pattern[car->currentPaternPos];

	car->pattern[car->currentPaternPos++] = PATTERN_END;

	car->isInFirstCorner = (car->currentPaternPos == 1)?1:0;

	if(car->pattern[car->currentPaternPos] == PATTERN_END)
	{
		car->currentPaternPos = 0;
		car->hasPattern = 0;
	}
	
	car->direction = newDirection(car, nextDir);

	return;
}

static int
newDirection(carADT car, int nextDir)
{
	if(nextDir==FORWARD)
		return car->direction;
		
	switch(car->direction)
	{
		case CAR_N:
			if(nextDir==RIGHT)
				return CAR_E;
			else
				return CAR_W;
		case CAR_S:
			if(nextDir==RIGHT)
				return CAR_W;
			else
				return CAR_E;
		case CAR_E:
			if(nextDir==RIGHT)
				return CAR_S;
			else
				return CAR_N;
		case CAR_W:
			if(nextDir==RIGHT)
				return CAR_N;
			else
				return CAR_S;
	}
	
	return -1;
}


//TODO:
static void
updatePosition(carADT car)
{
	switch(car->direction)
	{
		case CAR_N:
			car->y -= 1;
			if(car->y < 0)
				car->y = getCityHeight(car->city) - 1;
			break;
		case CAR_S:
			car->y += 1;
			if(car->y >= getCityHeight(car->city))
				car->y =  0;
			break;
		case CAR_E:
			car->x += 1;
			if(car->x >= getCityWidth(car->city))
				car->x =  0;
			break;
		case CAR_W:
			car->x -= 1;
			if(car->x < 0)
				car->x = getCityWidth(car->city) - 1;
			break;
	}
	
	return;
}


void
freeCar(carADT car)
{
	car->isKilled = 1;
	return;
}
